package net.sunbeta.test.log4j ;

import java.io.File ;
import java.io.FileInputStream ;
import java.io.FileOutputStream ;
import java.io.IOException ;
import java.text.SimpleDateFormat ;
import java.util.Date ;
import java.util.GregorianCalendar ;
import java.util.zip.GZIPOutputStream ;

import org.apache.log4j.FileAppender ;
import org.apache.log4j.helpers.LogLog ;
import org.apache.log4j.spi.LoggingEvent ;

/**
 * Appender that periodically compress current log.
 * You need only pass one setting - period after that log should compress.
 * Available periods are MINUTLY, HOURLY, DAILY, WEEKLY, MONTHLY.
 *
 * @author Pokidov.Dmitry
 *         Date: 27.08.2009
 */
public class CompressingRollingFileAppender extends FileAppender {
    enum Period {
        MINUTLY, HOURLY, DAILY, WEEKLY, MONTHLY
    }

    private GregorianCalendar rollDate ;
    private Period            period     = Period.HOURLY ;
    private SimpleDateFormat  dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm") ;

    @Override
    public void activateOptions() {
        super.activateOptions() ;
        setAppend(false) ;
    }

    /**
     * Period for rolling. By default - {@code HOURLY}
     * @param period new period. Available values: MINUTLY, HOURLY, DAILY, WEEKLY, MONTHLY.
     */
    public void setPeriod(String period) {
        try {
            this.period = Period.valueOf(period) ;
        } catch (IllegalArgumentException e) {
            LogLog.warn("No period with name [" + period + "]", e) ;
        }

        LogLog.debug("Set roll period to [" + this.period + "]") ;
    }

    @Override
    public void setAppend(boolean flag) {
        LogLog.warn("Compression rolling appender doesn't support append option(always set to false)") ;
    }

    protected void rollOver() {
        String currentFileName = getFile() ;
        File currentFile = new File(currentFileName) ;
        File zipFile = new File(currentFileName + "." + dateFormat.format(new Date()) + ".gz") ;
        try {
            /*
              actually, I use helper that read all bytes, using NIO.
              see http://jajatips.blogspot.com/2008/11/reading-from-inputstream.html
            */
            FileInputStream stream = new FileInputStream(currentFile) ;
            byte[] content = new byte[stream.available()] ;
            stream.read(content) ;

            GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new FileOutputStream(zipFile)) ;
            gzipOutputStream.write(content, 0, content.length) ;
            gzipOutputStream.finish() ;
            gzipOutputStream.flush() ;
            gzipOutputStream.close() ;

            setFile(currentFileName, false, getBufferedIO(), getBufferSize()) ;
            rollDate.add(getDateFieldByPeriod(), 1) ;
        } catch (IOException e) {
            LogLog.error("Error compress current log file [" + currentFile + "] to zip file [" + zipFile + "]", e) ;
        }
    }

    @Override
    protected void subAppend(LoggingEvent event) {
        if (rollDate == null) {
            rollDate = new GregorianCalendar() ;
            rollDate.add(getDateFieldByPeriod(), 1) ;
        }

        GregorianCalendar now = new GregorianCalendar() ;
        if (rollDate.before(now)) {
            rollOver() ;
        }

        super.subAppend(event) ;
    }

    private int getDateFieldByPeriod() {
        switch (period) {
            case MINUTLY:
                return GregorianCalendar.MINUTE ;
            case HOURLY:
                return GregorianCalendar.HOUR ;
            case DAILY:
                return GregorianCalendar.DAY_OF_MONTH ;
            case WEEKLY:
                return GregorianCalendar.WEEK_OF_MONTH ;
            case MONTHLY:
                return GregorianCalendar.MONTH ;
            default:
                return GregorianCalendar.MINUTE ;
        }
    }
}
